{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Interfaces in Python\n",
    "\n",
    "# References\n",
    "\n",
    "This tutorial is primarily inspired by chapters 9,11,12 in Luciano Ramalho's book \"Fluent Python\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 174,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# imports\n",
    "import math,decimal,random"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Introduction \n",
    "..._or, a very brief overview of object-oriented programming and type systems_.\n",
    "\n",
    "Definition: **objects** are “a location in memory having a value and possibly referenced by an identifier.”\n",
    "\n",
    "Objects have a type, which a classification scheme to reduce the probability of errors.\n",
    "\n",
    "In the programming language context:\n",
    "* variables refer to objects\n",
    "* a class is a template for creating objects\n",
    "* instances of classes are often used to represent objects\n",
    "\n",
    "There are important differences between types and classes, but we'll use them interchangably here.\n",
    "\n",
    "In Python, objects are almost always instances of classes. \n",
    "* Python class definitions are class instances themselves\n",
    "\n",
    "An object's class enumerates the object’s properties:\n",
    "* Identity (inheritance) \n",
    "* Attributes\n",
    "* Methods of interaction\n",
    "\n",
    "Definition: an **interface** (or **protocol**) is an agreed-upon set of rules by which unrelated objects interact\n",
    "\n",
    "The **key question**, in which Python offers an interesting choice: do we interact with objects according to their *identity*, or (some subset of) their *attributes*?\n",
    "\n",
    "For efficient Python programming, it's important to understand the costs and benefits for your answer to this question."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# \"Traditional\" Inheritence\n",
    "\n",
    "Paradigm: an object’s capabilities are defined by its _identity_...its unique attributes and its parents’ attributes. Sets of related attributes are assigned to an object via inheritence."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Animal:\n",
    "    \"\"\" \n",
    "    the Animal class can be used to describe something that has a well-defined number of legs\n",
    "    \"\"\"\n",
    "    n_legs = -1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 102,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a = Animal()\n",
    "a.n_legs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At this point, I'm not too worried about the mechanism by which object attributes are set. However, if the thing represented by an Animal instance truly always has a well-defined number of legs, and that number doesn't change (no starfish, no apputation), then we should set this at object creation time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Animal:\n",
    "    def __init__(self,n_legs=-1):\n",
    "        \"\"\"use the constructor's kw args 'n_legs' to set the number of legs\"\"\"\n",
    "        self.n_legs = n_legs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4"
      ]
     },
     "execution_count": 104,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cow = Animal(4)\n",
    "cow.n_legs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 105,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "snake = Animal(0)\n",
    "snake.n_legs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To represent a more nuanced set of identities, we need to provide more classes. For example, pets have names, but are also animals."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class Pet(Animal):\n",
    "    def __init__(self,name=None,n_legs=-1):\n",
    "        self.name = name\n",
    "        super().__init__(n_legs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The pet's name is Fido.\n",
      "It has 4 legs.\n"
     ]
    }
   ],
   "source": [
    "fido = Pet(name=\"Fido\",n_legs=4)\n",
    "print(\"The pet's name is \" + fido.name + '.')\n",
    "print(\"It has \" + str(fido.n_legs) + \" legs.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We're interested in more than simple, variable attributes. What about interaction? Remember that encapsulation of data provides a more robust framework for abstracting operations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Cat(Pet):\n",
    "    def make_a_sound(self):\n",
    "        return \"Meow\"\n",
    "class Dog(Pet):\n",
    "    def make_a_sound(self):\n",
    "        \"\"\"return a random sound\"\"\"\n",
    "        sounds = ['Arf','Grrrrrr']\n",
    "        return sounds[round(random.random())]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Kitty says \"Meow\"\n",
      "Buddy says \"Arf\"\n"
     ]
    }
   ],
   "source": [
    "pets = []\n",
    "pets.append(Cat(name=\"Kitty\"))\n",
    "pets.append(Dog(name=\"Buddy\"))\n",
    "for pet in pets:\n",
    "    print(pet.name + ' says \"' + pet.make_a_sound() + '\"')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### A more realistic example\n",
    "\n",
    "Add functionality via subclassing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class ListOfThings:\n",
    "    def __init__(self,x):\n",
    "        self.things = x\n",
    "    def get_the_things(self):\n",
    "        return self.things\n",
    "    \n",
    "class OrderedListOfThings(ListOfThings):\n",
    "    def get_the_things(self):\n",
    "        return sorted(self.things)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1, 3, 4, 2]"
      ]
     },
     "execution_count": 113,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a_list = ListOfThings([1,3,4,2])\n",
    "a_list.get_the_things()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 114,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1, 2, 3, 4]"
      ]
     },
     "execution_count": 114,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "an_ordered_list = OrderedListOfThings([1,3,4,2])\n",
    "an_ordered_list.get_the_things()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Problems\n",
    "Problems with defining use solely by inheritence: \n",
    "* Multiple inheritence is hard. What is the method resolution order?\n",
    "* Rigid/brittle structure...what if a base class definition changes?\n",
    "* There are Python-specific issues with inheriting from builtin classes\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Duck Typing\n",
    "\n",
    "\"Don’t check whether it is-a duck: check whether it quacks-like-a duck, walks-like-a duck, etc, etc, depending on exactly what subset of duck-like behavior you need to play your language-games with. (comp.lang.python, Jul. 26, 2000)\n",
    "— Alex Martelli\"\n",
    "\n",
    "The paradigm: classify and interact with objects according to their attributes, not according to their identity. \n",
    "\n",
    "While Python is very much an object-oriented programming language (i.e. objects _have_ identity, sometimes more than one), it broadly uses protocols rather than object identity to implement functionality.\n",
    "\n",
    "Another way of making the contrast: in the traditional inheritence model, we enable an object to do a useful thing by specifying its identity. In Python, we start with the useful thing, and define how objects must behave to do that thing. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# simple example: make two objects\n",
    "\n",
    "# this object has a clear sense of length\n",
    "x = [4,3,2,1]\n",
    "\n",
    "# what would the length of an integer be?\n",
    "y = 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 116,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4"
      ]
     },
     "execution_count": 116,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 117,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "object of type 'int' has no len()",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-117-cd3288d06d3d>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m: object of type 'int' has no len()"
     ]
    }
   ],
   "source": [
    "len(y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the previous example, we see that some objects follow the length protocol, and some don't. Specifically, the length protocol defines a _global_ function `len`, and the method by which it interfaces with objects...namely, their `__len__` method. \n",
    "\n",
    "With reference to the duck metaphor, the length protocol say (two different versions): \n",
    "* \"If you are a thing that has size or length, then you should implement a `__len__` method, so that unrelated objects know how to interact with you\". \n",
    "* \"If it acts like a thing with size or length, i.e. implements a `__len__` method, then I know how to get its length.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 118,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'three'"
      ]
     },
     "execution_count": 118,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# can we _force_ something to follow a protocol?\n",
    "\n",
    "def my_identity_function(x):\n",
    "    return x\n",
    "\n",
    "my_identity_function('three')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 119,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['__annotations__',\n",
       " '__call__',\n",
       " '__class__',\n",
       " '__closure__',\n",
       " '__code__',\n",
       " '__defaults__',\n",
       " '__delattr__',\n",
       " '__dict__',\n",
       " '__dir__',\n",
       " '__doc__',\n",
       " '__eq__',\n",
       " '__format__',\n",
       " '__ge__',\n",
       " '__get__',\n",
       " '__getattribute__',\n",
       " '__globals__',\n",
       " '__gt__',\n",
       " '__hash__',\n",
       " '__init__',\n",
       " '__kwdefaults__',\n",
       " '__le__',\n",
       " '__len__',\n",
       " '__lt__',\n",
       " '__module__',\n",
       " '__name__',\n",
       " '__ne__',\n",
       " '__new__',\n",
       " '__qualname__',\n",
       " '__reduce__',\n",
       " '__reduce_ex__',\n",
       " '__repr__',\n",
       " '__setattr__',\n",
       " '__sizeof__',\n",
       " '__str__',\n",
       " '__subclasshook__']"
      ]
     },
     "execution_count": 119,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# now explicitly set the value of the __len__ attribute\n",
    "\n",
    "setattr(my_identity_function,'__len__','my length!')\n",
    "dir(my_identity_function)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 120,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "object of type 'function' has no len()",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-120-25909769e695>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmy_identity_function\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m: object of type 'function' has no len()"
     ]
    }
   ],
   "source": [
    "len(my_identity_function)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[sad trombone]...functions types are defined in C and can't be truly modified, despite our modification of the object's namespace dictionary. \n",
    "\n",
    "### NOTE:\n",
    "Be careful with modifying or subclassing Python's builtin types: `str`, `int`, `float`, `list`, `dict`, and the like, as well as functions, class definition objects, and other such objects. Let's spend a minute seeing how this fails, then we'll hop back to our attempt to make a modifiable integer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 122,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'one': 1, 'two': [2, 2]}"
      ]
     },
     "execution_count": 122,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# make a dict that replaces the value with a pair of the value\n",
    "\n",
    "class DoppelDict(dict):\n",
    "    def __setitem__(self, key, value):\n",
    "        \"\"\"__setitem__ is called by the [] operator\"\"\"\n",
    "        super().__setitem__(key, [value] * 2)\n",
    "\n",
    "# set one k,v pair via the constructor\n",
    "dd = DoppelDict(one=1)\n",
    "# set another k,v pair with the square bracket operators\n",
    "dd['two'] = 2\n",
    "dd"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Because `dict` is a builtin class, it ignores attribute modifications applied via namespace changes.\n",
    "\n",
    "Let's now try to define a length-y decimal object. To define a modifiable class, let's use Python's `decimal` package, which is designed to represent a decimal interface. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 156,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Decimal('5')"
      ]
     },
     "execution_count": 156,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y = decimal.Decimal(5)\n",
    "y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 157,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "object of type 'decimal.Decimal' has no len()",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-157-cd3288d06d3d>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m: object of type 'decimal.Decimal' has no len()"
     ]
    }
   ],
   "source": [
    "len(y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Yay! It didn't work!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 158,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# now for our decimal with length\n",
    "\n",
    "# arbitrarily define length the number of digits to the left of the decimal point\n",
    "class LengthyDecimal(decimal.Decimal):\n",
    "    def __len__(self):\n",
    "        return math.floor(math.log10(self)) + 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 159,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Decimal('5')"
      ]
     },
     "execution_count": 159,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y = LengthyDecimal(5)\n",
    "y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 160,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 160,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# length is the integer representation of log10\n",
    "len(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 161,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 161,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y = LengthyDecimal(555.44)\n",
    "len(y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Yay!\n",
    "\n",
    "Now let's try an _integer_ that follows the length protocol."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 162,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n",
      "1\n"
     ]
    }
   ],
   "source": [
    "# let the Decimal class manage construction and all the other attributes\n",
    "# enforce integer qualities only when __len__ is called\n",
    "\n",
    "# arbitrarily define length as the log10 of the integer representation of the Decimal\n",
    "class LengthyInteger(decimal.Decimal):\n",
    "    def __len__(self):\n",
    "        return int(math.log(int(self),10))\n",
    "y = LengthyInteger(6)\n",
    "print(len(y))\n",
    "y = LengthyInteger(16)\n",
    "print(len(y))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nice! We made our object quack like a duck without defining it to be a duck. LengthyInteger/LengthDecimal doe _not_ inherit from a class that provides the needed functionality. \n",
    "\n",
    "You've seen an example of taking an object that does a thing, and modifying it to conform to an interface. But before you go off and start thinking about defining new interfaces, let's step back..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Interfaces and the python data model\n",
    "\n",
    "General idea: cooperate with essential protocols as much as possible. \n",
    "\n",
    "### Essential protocols\n",
    "Often defined in terms of global functions (`len`, `print`) acting on correspondingly named object attributes (`__len__`, `__repr__`).\n",
    "\n",
    "Other examples:\n",
    "* callability: implement `__call__`\n",
    "\n",
    "* iterability, iterables, sequence are related protocols\n",
    "\n",
    "![title](protocols.png)\n",
    "\n",
    "* a \"file-like\" object: implements the functions needed to read / write bytes-like data.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## A Pythonic object\n",
    "\n",
    "This example implements many common Python interfaces. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 163,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# copied directy from \"Fluent Python\", pp. 298-300\n",
    "\n",
    "from array import array \n",
    "import reprlib\n",
    "import math\n",
    "import numbers\n",
    "import functools\n",
    "import operator\n",
    "import itertools\n",
    "\n",
    "class Vector: \n",
    "    typecode = 'd'\n",
    "    def __init__(self, components):\n",
    "        self._components = array(self.typecode, components)\n",
    "    def __iter__(self):\n",
    "        return iter(self._components)\n",
    "    def __repr__(self):\n",
    "        components = reprlib.repr(self._components) \n",
    "        components = components[components.find('['):-1] \n",
    "        return 'Vector({})'.format(components)\n",
    "    def __str__(self):\n",
    "        return str(tuple(self))\n",
    "    def __bytes__(self):\n",
    "        return (bytes([ord(self.typecode)]) +\n",
    "            bytes(self._components))\n",
    "    def __eq__(self, other):\n",
    "        return (len(self) == len(other) and\n",
    "                all(a == b for a, b in zip(self, other)))\n",
    "    def __hash__(self):\n",
    "        hashes = (hash(x) for x in self)\n",
    "        return functools.reduce(operator.xor, hashes, 0)\n",
    "    def __abs__(self):\n",
    "        return math.sqrt(sum(x * x for x in self))\n",
    "    def __bool__(self):\n",
    "        return bool(abs(self))\n",
    "    def __len__(self):\n",
    "        return len(self._components)\n",
    "    def __getitem__(self, index): \n",
    "        cls = type(self)\n",
    "        if isinstance(index, slice):\n",
    "            return cls(self._components[index])\n",
    "        elif isinstance(index, numbers.Integral): return self._components[index]\n",
    "        else:\n",
    "            msg = '{.__name__} indices must be integers' \n",
    "            raise TypeError(msg.format(cls))\n",
    "    \n",
    "    shortcut_names = 'xyzt'\n",
    "\n",
    "    def __getattr__(self, name): \n",
    "        cls = type(self)\n",
    "        if len(name) == 1:\n",
    "            pos = cls.shortcut_names.find(name) \n",
    "            if 0 <= pos < len(self._components):\n",
    "                return self._components[pos]\n",
    "        msg = '{.__name__!r} object has no attribute {!r}' \n",
    "        raise AttributeError(msg.format(cls, name))\n",
    "    \n",
    "    def angle(self, n):\n",
    "        r = math.sqrt(sum(x * x for x in self[n:])) \n",
    "        a = math.atan2(r, self[n-1])\n",
    "        if (n == len(self) - 1) and (self[-1] < 0):\n",
    "            return math.pi * 2 - a \n",
    "        else:\n",
    "            return a\n",
    "    \n",
    "    def angles(self):\n",
    "        return (self.angle(n) for n in range(1, len(self)))\n",
    "    \n",
    "    def __format__(self, fmt_spec=''):\n",
    "        if fmt_spec.endswith('h'): # hyperspherical coordinates\n",
    "            fmt_spec = fmt_spec[:-1]\n",
    "            coords = itertools.chain([abs(self)],\n",
    "                                     self.angles())\n",
    "            outer_fmt = '<{}>' \n",
    "        else:\n",
    "            coords = self\n",
    "            outer_fmt = '({})'\n",
    "        components = (format(c, fmt_spec) for c in coords) \n",
    "        return outer_fmt.format(', '.join(components))\n",
    "    \n",
    "    @classmethod\n",
    "    def frombytes(cls, octets):\n",
    "        typecode = chr(octets[0])\n",
    "        memv = memoryview(octets[1:]).cast(typecode) \n",
    "        return cls(memv)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "# So what should I do?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Don't...\n",
    "\n",
    "* Don't implement every possible interface for every object you build. Just do enough that it works.\n",
    "* Don't define new interfaces. The Python data model includes a very robust set of interface definitions.\n",
    "* Don't define new abstract base classes (unless you're building a brand new framework)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Type Tests\n",
    "Don't test an object's type. Test its conformity to the protocol that matters. Use try blocks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 164,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def object_to_str(obj):\n",
    "    try:\n",
    "        return str(obj)\n",
    "    except TypeError:\n",
    "        return \"Don't know how to represent argument as a string\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 165,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"{'a': 1, 'b': [5]}\""
      ]
     },
     "execution_count": 165,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "object_to_str({'a':1,'b':[5]})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 166,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"<_io.TextIOWrapper name='tmp.txt' mode='w' encoding='UTF-8'>\""
      ]
     },
     "execution_count": 166,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "object_to_str(open('tmp.txt','w'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Type Tests II\n",
    "Do test an object's interface with an Abstract Base Class"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 175,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 175,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from collections import abc\n",
    "my_dict = {}\n",
    "isinstance(my_dict, abc.Mapping)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Object building\n",
    "Construct an object so that it follows the protocols that define the desired functionality. "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
